package com.baizhi.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baizhi.dao.es.EsArticleDao;
import com.baizhi.dao.jpa.ArticleDao;
import com.baizhi.dao.jpa.ArticleDataDao;
import com.baizhi.dao.mongo.CommentDao;
import com.baizhi.domain.dto.ArticleEditDto;
import com.baizhi.domain.dto.ArticlePageDto;
import com.baizhi.domain.dto.ArticleSaveDto;
import com.baizhi.domain.dto.ArticleSearchDto;
import com.baizhi.domain.es.EsArticle;
import com.baizhi.domain.jpa.Article;
import com.baizhi.domain.jpa.ArticleData;
import com.baizhi.domain.mongo.Comment;
import com.baizhi.domain.vo.ArticleDetailVo;
import com.baizhi.domain.vo.PageVo;
import com.baizhi.service.ArticleService;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
@Transactional
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private ArticleDao articleDao;

    @Autowired
    private ArticleDataDao articleDataDao;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private EsArticleDao esArticleDao;

    @Autowired
    private CommentDao commentDao;

    // 查询文章列表
    @Override
    public PageVo<List<Article>> findPage(ArticlePageDto param) {
        // 拼接查询条件
        Specification<Article> specification = new Specification<Article>() {
            @Override
            public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> list = new ArrayList<>();
                if (!StringUtils.isBlank(param.getTitle())) {
                    Predicate predicate = criteriaBuilder.equal(root.get("title").as(String.class), param.getTitle());
                    list.add(predicate);
                }
                if (!StringUtils.isBlank(param.getAuthor())) {
                    Predicate predicate = criteriaBuilder.equal(root.get("author").as(String.class), param.getAuthor());
                    list.add(predicate);
                }
                return criteriaBuilder.and(list.toArray(new Predicate[]{}));
            }
        };
        // 分页条件
        Pageable pageable = PageRequest.of(param.getPage()-1, param.getSize(), Sort.by(Sort.Order.desc("createTime")));
        Page<Article> page = articleDao.findAll(specification, pageable);

        // 封装结果集
        PageVo<List<Article>> result = new PageVo<>();
        result.setTotalSize(page.getTotalElements());
        result.setTotalPage(page.getTotalPages());
        result.setPageSize(page.getSize());
        result.setData(page.getContent());
        return result;
    }

    // 查看文章详情
    @Override
    public ArticleDetailVo findById(Integer id) {
        // 查询文章信息
        Optional<Article> optional = articleDao.findById(id);
        Article article = optional.get();
        if (article == null) {
            new RuntimeException("data not found !!!");
        }
        ArticleDetailVo result = new ArticleDetailVo();
        BeanUtils.copyProperties(article, result);
        if (article.getArticleData() != null) {
            result.setContent(article.getArticleData().getContent());
        }
        // 查询评论信息
        result.setComments(commentDao.findByAid(id));
        return result;
    }

    // 保存文章
    @Override
    public void saveArticle(ArticleSaveDto param) {
        // 封装数据
        Article article = new Article();
        BeanUtils.copyProperties(param, article);
        article.setCreateTime(new Date());
        ArticleData articleData = new ArticleData();
        articleData.setContent(param.getContent());
        // 建立关系
        article.setArticleData(articleData);
        articleData.setArticle(article);

        // 1.向mysql中保存文章和文章详情
        articleDataDao.save(article.getArticleData());
        articleDao.save(article);

        // 2.清空redis缓存
        redisTemplate.delete("articles");

        // 3.向es中保存数据
        EsArticle esArticle = new EsArticle();
        BeanUtils.copyProperties(article, esArticle);
        esArticle.setId(article.getAid());
        esArticle.setContent(article.getArticleData().getContent());
        esArticleDao.save(esArticle);
    }

    // 更新文章
    @Override
    public void updateArticle(ArticleEditDto param) {
        Optional<Article> optional = articleDao.findById(param.getAid());
        Article article = optional.get();
        if (article == null) {
            new RuntimeException("data not found !!!");
        }
        // 封装数据
        BeanUtils.copyProperties(param, article);
        ArticleData articleData = article.getArticleData();
        articleData.setContent(param.getContent());
        // 建立关系
        article.setArticleData(articleData);
        articleData.setArticle(article);

        // 1.更新mysql中文章和文章详情
        articleDao.save(article);
        articleDataDao.updateContentByAid(article.getAid(), article.getArticleData().getContent());

        // 2.清空redis缓存
        redisTemplate.delete("articles");

        // 3.向es中修改数据
        EsArticle esArticle = new EsArticle();
        BeanUtils.copyProperties(article, esArticle);
        esArticle.setId(article.getAid());
        esArticle.setContent(article.getArticleData().getContent());
        esArticleDao.save(esArticle);
    }

    // 删除文章
    @Override
    public void deleteByAid(Integer aid) {
        // 1.删除mysql中文章和文章详情
        articleDataDao.deleteByAid(aid);
        articleDao.deleteById(aid);

        // 2.删除mongodb中相关评论
        List<Comment> comments = commentDao.findByAid(aid);
        commentDao.deleteAll(comments);

        // 3.清空redis缓存
        redisTemplate.delete("articles");

        // 4.向es中删除数据
        esArticleDao.deleteById(aid);
    }

    // 最新文章列表
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public List<Article> findNewArticleList() {
        // 1.先从redis中获取
        String value = redisTemplate.opsForValue().get("articles");

        // 2.如果redis中没有，去数据库中查询，查询之后再存入redis
        if (StringUtils.isBlank(value)) {
            Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Order.desc("createTime")));
            Page<Article> page = articleDao.findAll(pageable);
            List<Article> content = page.getContent();
            // 将结果转为String，存入redis
            if (!CollectionUtils.isEmpty(content)) {
                value = JSONObject.toJSONString(content);
                redisTemplate.opsForValue().set("articles", value);
            }
        }

        // 3.将结果转成list
        return JSONObject.parseArray(value, Article.class);
    }

    // 文章检索
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public PageVo<List<EsArticle>> searchArticle(ArticleSearchDto param) {
        // 设置分页条件
        Pageable pageable = PageRequest.of(param.getPage()-1, param.getSize());
        Page<EsArticle> page = esArticleDao.findByTitleOrContent(pageable, param.getKeyword(), param.getKeyword());

        // 封装结果集
        PageVo<List<EsArticle>> result = new PageVo<>();
        result.setTotalSize(page.getTotalElements());
        result.setTotalPage(page.getTotalPages());
        result.setPageSize(page.getSize());
        result.setData(page.getContent());
        return result;
    }

    // 清空数据
    @Override
    public void cleanData() {
        // 1.清空mysql数据
        articleDataDao.dropTable();
        articleDao.dropTable();

        // 2.清空mongodb数据
        commentDao.deleteAll();

        // 3.清空redis数据
        redisTemplate.delete("articles");

        // 4.清空es数据
        esArticleDao.deleteAll();
    }
}
